/*
 * $Id: ShellApp.cc,v 1.14 2007-06-21 10:49:01 baconevi Exp $
 */

#include "Globals.h"
#include "ShellApp.h"
#include "MainApp.h"

#include <xp/bas/sysapi.h>
#include <xp/bas/stdlib.h>
#include <xpp/bas/MutexLock.h>

#ifdef _WIN32
	#include <windows.h>
#endif

ShellApp::ShellApp ()
{
#ifdef _WIN32
	xp_memset (&proc_info, 0, XP_SIZEOF(proc_info));
	child = false;
#else
	parent_pid = 0;
	nanny_pid = 0;
	child_pid = 0;
#endif

	setup_signal_handlers ();
}

ShellApp::~ShellApp () 
{
	restore_signal_handlers ();
}

#ifdef _WIN32
void ShellApp::setChild (bool child)
{
	this->child = child;
}
#endif

int ShellApp::run ()
{
	stop_requested = false;

#ifdef _WIN32
	if (this->child)
	{
		return run_main_task ();
	}
	else
	{
		load_ini_file ();
		if (app_mode & APP_MODE_FOREGROUND) 
		{
			xp_log_settarget (&app_log,
				xp_log_gettarget(&app_log) | XP_LOG_CONSOLE);
		}

		HANDLE event = CreateEvent (NULL, TRUE, FALSE, APP_EVENT_NAME);
		if (event == INVALID_HANDLE_VALUE)
		{
			XP_LOG1 (&app_log, APP_NAME, XP_LOG_INFO,
				XP_T("cannot create the event - %s"), 
				APP_EVENT_NAME);
			return -1;
		}

		while (!stop_requested)
		{
			bool ok = false;

			if (run_nanny() == -1) return -1;

			while (!stop_requested)
			{
				if (WaitForSingleObject (proc_info.hProcess, 1000) == WAIT_OBJECT_0) 
				{
					ok = true;
					break;
				}

			}

			if (stop_requested && !ok)
			{
				SetEvent (event);

				for (int i = 0; i < 60; i++)
				{	
					/*
					XP_LOG1 (&app_log, APP_NAME, XP_LOG_INFO,
						XP_T("waiting for the child termination #%d"), i);
					*/
					if (WaitForSingleObject (proc_info.hProcess, 1000) == WAIT_OBJECT_0) 
					{
						ok = true;
						break;
					}
				}

				if (!ok)
				{
					/*
					XP_LOG1 (&app_log, APP_NAME, XP_LOG_INFO,
						XP_T("terminating process %d\n"), (int)proc_info.hProcess);
					*/
					TerminateProcess (proc_info.hProcess, 999);
				}
			}

			DWORD retval;
			GetExitCodeProcess (proc_info.hProcess, &retval);

			CloseHandle (proc_info.hProcess);
			CloseHandle (proc_info.hThread);

			if (retval == 0) break;
			xp_sleep (1000);
		}

		CloseHandle (event);
	}
#else
	parent_pid = xp_getpid();

	while (!stop_requested) 
	{
		int status;

		nanny_pid = xp_fork ();
		if (nanny_pid == -1) return -1;
		if (nanny_pid == 0) return run_nanny ();

		for (;;) 
		{
			xp_pid_t wpid = xp_wait (nanny_pid, &status, 0);
			if (wpid == -1) return -1;
			if (wpid == nanny_pid) break;
		}
		if (XP_WIFEXITED(status) && XP_WEXITCODE(status) == 0) break;

		xp_sleep (1000);
	}
#endif

	return 0;
}

#ifdef _WIN32
int ShellApp::run_nanny ()
{
	xp_char_t path[Path::MAX_LEN+1];
	xp_char_t cmd[Path::MAX_LEN+1+30]; // space for --child && --foreground
	xp_size_t len;

	len = GetModuleFileName (NULL, path, XP_COUNTOF(path));
	if (len == 0)
	{
		XP_LOG0 (&app_log, APP_NAME, XP_LOG_CRITICAL,
			XP_T("cannot get module file name"));
		return -1;
	}

	STARTUPINFO si;

	xp_memset (&si, 0, XP_SIZEOF(si));
	si.cb = XP_SIZEOF(si);

	xp_memset (&proc_info, 0, XP_SIZEOF(proc_info));

	const xp_char_t* fg = XP_T("");
	if (app_mode & APP_MODE_FOREGROUND) fg = XP_T("--foreground ");

	xp_char_t* bs = xp_strrchr (path, XP_T('\\'));
	if (bs == XP_NULL)
	{
		xp_sprintf (cmd, XP_COUNTOF(cmd), XP_T("%s %s--child"), path, fg);
	}
	else
	{
		xp_sprintf (cmd, XP_COUNTOF(cmd), XP_T("%s %s--child"), ++bs, fg);
	}

	BOOL status = CreateProcess (
		path,  // application name
		cmd,  // command line
		NULL,  // process attributes 
		NULL,  // thread attributes
		FALSE, // inherit handles
		CREATE_NEW_PROCESS_GROUP, // creation flags
		NULL,  // environment
		NULL,  // current directory
		&si,   // startup info
		&proc_info);  // process information
	if (!status)
	{
		XP_LOG1 (&app_log, APP_NAME, XP_LOG_CRITICAL,
			XP_T("cannot create child process - %s"), path);
		return -1;
	}

	return 0;
}

#else
int ShellApp::run_nanny ()
{
	// nanny_pid would be still 0 without this in the nanny itself
	nanny_pid = xp_getpid ();  
	xp_setpgid (nanny_pid, 0);

	child_pid = xp_fork ();
	if (child_pid == -1) return -1;
	if (child_pid == 0) 
	{
		try 
		{
			//return run_main_task ();
			xp_exit (run_main_task ());
		}
		catch (...) { return -1; }
	}

	int status;
	for (;;) 
	{
		xp_pid_t wpid = xp_wait (child_pid, &status, 0);
		if (wpid == -1) return -1;
		if (wpid == child_pid) break;
	}
	if (XP_WIFEXITED(status) && XP_WEXITCODE(status) == 0) return 0;

	xp_kill (0, SIGKILL); // xp_kill all processes in my process group
	return -1;
}
#endif

int ShellApp::run_main_task ()
{
#ifndef _WIN32
	child_pid = xp_getpid ();  // just in case.
#endif

	load_ini_file ();
	if (app_mode & APP_MODE_FOREGROUND) 
	{
		xp_log_settarget (&app_log,
			xp_log_gettarget(&app_log) | XP_LOG_CONSOLE);
	}

	MainApp app;
	{
		MutexLock lock (&app_main_mutex);
		app_main = &app;
	}

	int ret_code;
	if (app.run() == -1) {
		XP_LOG0 (&app_log, APP_NAME, XP_LOG_CRITICAL,
			XP_T("failed to launch the main application"));
		ret_code = -1;
	}	
	else ret_code = app.returnCode();

	{
		MutexLock lock (&app_main_mutex);
		app_main = XP_NULL;
	}

	return ret_code;
}

int ShellApp::stop ()
{
	stop_requested = true;
	MutexLock lock (&app_main_mutex);
	if (app_main == XP_NULL) return -1;
	return app_main->stop ();
}

int ShellApp::shutdown ()
{
	stop_requested = true;
	MutexLock lock (&app_main_mutex);
	if (app_main == XP_NULL) return -1;
	return app_main->stop ();
}

void ShellApp::handleSignal (int sig)
{
#ifdef _WIN32
	/*return*/ this->stop ();
#else
	xp_pid_t pid = xp_getpid();

	if (pid == parent_pid) {
		xp_kill (nanny_pid, sig);
		//return 0;
	}
	else if (pid == nanny_pid) {
		xp_kill (child_pid, sig);
		//return 0;
	}
	else /*return*/ this->stop ();
#endif
}

void ShellApp::setup_signal_handlers ()
{
	// ignoreSignal(32) caused the system to freeze on Linux
	//ignoreAllSignals (); 
	
	//ignoreSignal (SIGCHLD);
#ifdef SIGPIPE
	ignoreSignal (SIGPIPE);
#endif

#ifdef SIGHUP
	manageSignal (SIGHUP);
#endif
#ifdef SIGINT
	manageSignal (SIGINT);
#endif
#ifdef SIGQUIT
	manageSignal (SIGQUIT);
#endif
#ifdef SIGTERM
	manageSignal (SIGTERM);
#endif
}

void ShellApp::restore_signal_handlers ()
{
	//defaultifyAllSignals ();

#ifdef SIGCHLD
	defaultifySignal (SIGCHLD);
#endif
#ifdef SIGPIPE
	defaultifySignal (SIGPIPE);
#endif
#ifdef SIGHUP
	defaultifySignal (SIGHUP);
#endif
#ifdef SIGINT
	defaultifySignal (SIGINT);
#endif
#ifdef SIGQUIT
	defaultifySignal (SIGQUIT);
#endif
#ifdef SIGTERM
	defaultifySignal (SIGTERM);
#endif
}

void ShellApp::load_ini_file ()
{
	app_env_path.chmod (APP_FILE_PERM);

	app_env.preLoad ();
	if (app_env_path.read (&app_env) == -1) 
	{
		XP_LOG1 (&app_log, APP_NAME, XP_LOG_INFO,
			XP_T("cannot load %s. using all default values"), 
			app_env_path.name());
	}
	app_env.postLoad ();
}
